/**
 * s_mpd.c
 * MPD source for TrayInfo.
 * MPD support added by Laszlo Ashin <kodest@gmail.com>
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> /* close() */
#include <netdb.h>

#include "trayinfo.h"
#include "utils.h"

/* maximal length of output can take */
#define MAXLEN	128

/* size of the receive buffer */
#define RECVBUFSIZE	256

/* MPD server connect parameters */
static char *port;
static char *host;
static char *pass;

static int maxlen;

static char *file = NULL;
static char *artist = NULL;
static char *title = NULL;

static int playing = 0;
static enum {
	INIT,
	STATUS_SENT,
	STATUS_CAUGHT,
	SONG_SENT,
	SONG_CAUGHT
} status;

/* starts a query for the current song */
static void
mpd_send_request(int fd)
{
	if (status == INIT) {
		sendall(fd, "status\n", 7, 0);
		status = STATUS_SENT;
	} else if (status == STATUS_CAUGHT && playing) {
		if (file) free(file);
		if (artist) free(artist);
		if (title) free(title);
		file = artist = title = NULL;
		sendall(fd, "currentsong\n", 12, 0);
		status = SONG_SENT;
	} else {
		status = INIT;
	}
}

/* show the name of current song */
static int
mpd_write(struct timeval *now, int fd)
{
	int n = 0;

	if (playing) {
		char buf[MAXLEN];

		if (maxlen > MAXLEN) maxlen = MAXLEN;
		if (artist && title) {
			n = snprintf(buf, maxlen, "%s - %s", artist, title);
		} else if (file) {
			n = snprintf(buf, maxlen, "%s", file);
		}
		if (n) {
			if (n < 0 || n >= maxlen) {
				buf[maxlen - 1] = '\0';
				buf[maxlen - 2] = '.';
				buf[maxlen - 3] = '.';
			}
			fwrite(buf, strlen(buf), 1, stdout);
		}
	}

	if (fd && status == INIT)
		mpd_send_request(fd);

	return n;
}

/* open a connection to the MPD server */
static int
mpd_open(void)
{
	struct addrinfo hints;
	struct addrinfo *res, *rp;
	int fd, s;
	
	memset(&hints, 0, sizeof(hints));
	hints.ai_flags = AI_ADDRCONFIG;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = 0;

	s = getaddrinfo(
		host ? host : "localhost",
		port ? port : "6600",
		&hints, &res
	);
	if (s) {
		fprintf(stderr, "mpd: getaddrinfo(): %s\n", gai_strerror(s));
		return 0;
	}

	for (rp = res; rp; rp = rp->ai_next) {
		fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (fd == -1)
			continue;

		if (!connect(fd, rp->ai_addr, rp->ai_addrlen))
			break;

		close(fd);
	}

	freeaddrinfo(res);

	if (!rp) {
		fprintf(stderr, "mpd: unable to connect\n");
		return 0;
	}

	if (pass && *pass) {
		char *buf;

		buf = (char *)malloc(11 + strlen(pass));
		strcpy(buf, "password ");
		strcat(buf, pass);
		strcat(buf, "\n");
		sendall(fd, buf, strlen(buf), 0);
		free(buf);
	}
	status = INIT;
	playing = 0;
	mpd_send_request(fd);
	return fd;
}

/* closes the connection */
static void
mpd_close(int fd)
{
	close(fd);
	playing = 0;
	if (file) free(file);
	if (artist) free(artist);
	if (title) free(title);
	file = artist = title = NULL;
}

/* parse received data */
static void
mpd_process_recved(char *s, int fd)
{
	if (status == STATUS_SENT) {
		if (strstartswith(s, "state: ")) {
			playing = strstartswith(s + 7, "play");
		} else if (!strcmp(s, "OK")) {
			status = STATUS_CAUGHT;
			mpd_send_request(fd);
		}
	} else if (status == SONG_SENT) {
		if (strstartswith(s, "file: ")) {
			char *t = strrchr(s, '/');

			t = t ? t + 1 : s + 6;
			if (file) free(file);
			file = strdup(t);
		} else if (strstartswith(s, "Artist: ")) {
			if (artist) free(artist);
			artist = strdup(s + 8);
		} else if (strstartswith(s, "Title: ")) {
			if (title) free(title);
			title = strdup(s + 7);
		} else if (!strcmp(s, "OK")) {
			status = SONG_CAUGHT;
			mpd_send_request(fd);
		}
	}
}

/* get data from MPD server */
static ssize_t
mpd_read(int fd)
{
	static char buf[RECVBUFSIZE];
	static size_t sord = 0; /* size of received data */
	ssize_t n = recv(fd, buf + sord, sizeof(buf) - sord, 0);
	if (n > 0) {
		char *sep;

		sord += n;
		while ((sep = memchr(buf, '\n', sord))) {
			int l;

			*sep++ = '\0';
			l = sep - buf;
			mpd_process_recved(buf, fd);
			sord -= l;
			memmove(buf, buf + l, sord);
		}
		/* check for overflow */
		if (sord == sizeof(buf)) {
			/* simply drop everything */
			sord = 0;
		}
	}
	return n;
}

static void
mpd_init(void)
{
	port = pass = host = NULL;
	maxlen = MAXLEN;
}

static void
mpd_done(void)
{
	if (port) free(port);
	if (pass) free(pass);
	if (host) free(host);
	port = pass = host = NULL;
}

static int
mpd_cfg_port(const char *s)
{
	if (port) free(port);
	port = strdup(s);

	return 0;
}

static int
mpd_cfg_host(const char *s)
{
	if (host) free(host);
	host = strdup(s);

	return 0;
}

static int
mpd_cfg_maxlen(const char *s)
{
	return cfgstr2int(s, &maxlen);
}

static int
mpd_cfg_pass(const char *s)
{
	if (pass) free(pass);
	pass = strdup(s);

	return 0;
}

/* register MPD source */
void
regsrc_mpd(src_t *src)
{
	src->name = "mpd";
	src->init = mpd_init;
	src->done = mpd_done;
	src->write = mpd_write;
	src->open = mpd_open;
	src->close = mpd_close;
	src->read = mpd_read;
	
	cfg_add_handler("mpd-port", mpd_cfg_port);
	cfg_add_handler("mpd-host", mpd_cfg_host);
	cfg_add_handler("mpd-maxlen", mpd_cfg_maxlen);
	cfg_add_handler("mpd-pass", mpd_cfg_pass);
}
